﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac.Core;
using Module = Autofac.Module;

namespace Play.Messaging
{
	class MessageInterceptionModule : Module
	{
		protected override void AttachToComponentRegistration(
			IComponentRegistry componentRegistry,
			IComponentRegistration registration)
		{
			registration.Activating += (s, e) => ActivateMessaging(e.Instance);
		}

		private readonly List<WeakReference> _instances = new List<WeakReference>();

		private void ActivateMessaging(object instance)
		{
			ActivateSendMessage(instance);
			ActivateHandleMessage(instance);
		}

		private void ActivateSendMessage(object instance)
		{
			// TODO: Event wieder abhängen / weak machen
			instance.GetType()
				.GetEvents()
				.Where(it => it.EventHandlerType == typeof(MessageEventHandler))
				.ForEach(it => it.AddEventHandler(
					instance, new MessageEventHandler(message =>
						DispatchMessage(message, instance))));
		}

		private void DispatchMessage(object message, object sender)
		{
			List<object> realInstances;
			lock (_instances)
			{
				realInstances = _instances
					.Select(it => it.Target)
					.Where(it => it != null)
					.ToList();
			}

			realInstances.ForEach(it =>
					{
						var handleMessageOverload = GetPotentialMethodHandler(it)
							.FirstOrDefault(it2 => it2.GetParameters()[0].ParameterType == message.GetType());
						if (handleMessageOverload != null)
							handleMessageOverload.Invoke(it, new[] { message });
					});
		}

		private IEnumerable<MethodInfo> GetPotentialMethodHandler(object instance)
		{
			return instance.GetType()
				.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
				.Where(it => it.Name == "HandleMessage")
				.Where(it => it.ReturnType == typeof(void))
				.Where(it => it.GetParameters().Length == 1);
		}

		private void ActivateHandleMessage(object instance)
		{
			lock (_instances)
			{
				_instances.Add(new WeakReference(instance));
			}
		}
	}
}
